package util;

import java.lang.reflect.*;
import java.math.*;
import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.*;

// We choose to only support hmacSHA256 as MAC algorithm and not support others. Could be easy implemented to support
// multiple algorithms.

public class Crypto {
	// Generates an encrypted code, based on a key and a value.
	// In this case the value is intended to be a Unix timestamp in seconds.
	public static String generateTOTP(String key, String time) {
		String result = null;

		// Time should be atleast 16, should never happen if the time is retrieved directly from the OS but in
		// case it comes from another source (database for example) and there goes something wrong we just prepend
		// 0 until we have 16.
		while(time.length() < 16) {
			time = "0" + time;
		}

		// Convert key and time to a byte array. We convert the individual character from the key to integers
		// since the byte array is created from the integer values of the string.
		byte[] hash = hmacSHA256(string2Bytes(convertStringToIntegerValues(key)), string2Bytes(time));

		// To get the code we bitshift around the hash to get integers from different parts of the hash.
		int offset = hash[hash.length - 1] & 0xf;
		int binary = ((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16) | ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff);
		
		// The modus is needed so the code is 6 digits long, modify the length of the number to get a
		// longer or shorter code.
		result = Integer.toString(binary % 1000000);
		
		 // Shouldn't get here, but just incase for some reason the bitshifting went wrong and the modus of the number is less then 6
		while(result.length() < 6) {
			result = "0" + result;
		}

		return result;
	}
	
	// Convert each character in the string to a digit, if the string contains digits they
	// just keep the correct value. We do convert it to integers but the method returns a String
	// because the output of this method needs to be a string in the next method that is called after
	// this and this is just lazy so we don't need to convert it there first.
	private static String convertStringToIntegerValues(String input) {
		String output = "";
		for(int i = 0; i < input.length(); i++) {
			output += Character.getNumericValue(input.charAt(i));
		}
		
		return output;
	}

	// Make from the input string a byte array
	private static byte[] string2Bytes(String input) {
		byte[] bytes = new BigInteger("10" + input, 16).toByteArray();

		// Copy all the bytes, not just the first
		byte[] output = new byte[bytes.length - 1];
		for(int i = 0; i < output.length; i++) {
			output[i] = bytes[i + 1];
		}

		return output;
	}

	// This method computes a Hashed Message Authentication Code (HMAC) from a key and text, which in our
	// case is the serial and the time. We use the Java Cryptography Extension (JCE) to compute the HMAC.
	private static byte[] hmacSHA256(byte[] keyBytes, byte[] text) {
		try {
			Mac mac = Mac.getInstance("HmacSHA512");
			SecretKeySpec macKey = new SecretKeySpec(keyBytes, "RAW");
			mac.init(macKey);

			return mac.doFinal(text);
		} catch(GeneralSecurityException e) {
			throw new UndeclaredThrowableException(e);
		}
	}
}
